<?php

namespace App\Http\Controllers\Order;

use App\Http\Controllers\Asset\FilterableBundleCriterionTrait;
use App\Http\Controllers\Controller;
use App\Http\Controllers\pagniateable;
use App\Http\Resources\CommonModelResource;
use App\Http\Resources\KeyValueResource;
use App\Models\Asset;
use App\Models\AssetCategory;
use App\Models\AssetStatus;
use App\Models\AssetStock;
use App\Models\Fee\FeeFormula;
use App\Models\FilterableAssetBundle\FilterableAssetBundle;
use App\Models\Order\Order;
use App\Models\Order\OrderBundle;
use App\Models\Order\OrderBundleItem;
use App\Models\Order\OrderExtraFee;
use App\Models\Order\OrderItem;
use App\Models\Order\OrderStatus;
use App\Models\Order\PaymentMethod;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Lrental\FeeFormula\FeeFormulaUtils;
use Lrental\Utils\OrderNumberGenerator;
use Lrental\Utils\OrderStatusEnum;
use PDF;

class OrderController extends Controller
{
    use pagniateable, FilterableBundleCriterionTrait;

    public function list()
    {
        $query = Order::select("*");
        return $this->paginate($query);
    }

    private function updateStatus($id, $statusId): Order
    {
        $order = Order::findOrFail($id);
        $order->update([
            "status_id" => $statusId
        ]);
        return $order;
    }

    /**
     * 订单确认出库
     * @param $id
     * @return \Illuminate\Http\JsonResponse
     */
    public function lend($id)
    {
        $order = Order::findOrFail($id);
        $now = Carbon::now();
        $order->update([
            "status_id" => OrderStatusEnum::ACTIVE,
            "real_rental_out_date" => $now
        ]);
        $orderItemIds = request()->get("ids");
        $ids = collect($orderItemIds);
        $itemIds = $ids->filter(function ($id) {
            return !Str::of($id)->startsWith("bundle_");
        });
        $bundleIds = $ids->filter(function ($id) {
            return Str::of($id)->startsWith("bundle_");
        })->map(function ($id) {
            return Str::of($id)->replace("bundle_", "");
        });
        OrderItem::whereIn("id", $itemIds)->update([
            "rental_out_at" => $now
        ]);
        OrderBundle::whereIn("id", $bundleIds)->update([
            "rental_out_at" => $now
        ]);
        OrderBundleItem::whereIn('order_bundle_id', $bundleIds)->update([
            "rental_out_at" => $now
        ]);
        $orderItems = $order->items;
        $orderBundles = $order->bundles()->with(['items.rentalable'])->get();
        return response()->json(compact(['order', 'orderItems', 'orderBundles']));
    }

    public function cancel($id)
    {
        $order = $this->updateStatus($id, OrderStatusEnum::CANCELED);
        $order->refresh();
        return response()->json(compact(['order']));
    }

    public function complete($id)
    {
        $order = Order::findOrFail($id);
        $order->update([
            "status_id" => OrderStatusEnum::COMPLETED,
            "real_return_date" => Carbon::now()
        ]);
        $order->refresh();
        return response()->json(compact(['order']));
    }

    /**
     * 订单资产归还
     * @param $id
     * @return \Illuminate\Http\JsonResponse
     */
    public function return($id)
    {

        $now = Carbon::now();
        $ids = collect(request()->get("ids"));

        $itemIds = $ids->filter(function ($id) {
            return !Str::of($id)->startsWith("bundle_");
        });
        $bundleIds = $ids->filter(function ($id) {
            return Str::of($id)->startsWith("bundle_");
        })->map(function ($id) {
            return Str::of($id)->replace("bundle_", "");
        });
        OrderItem::whereIn("id", $itemIds)->update([
            "return_at" => $now
        ]);
        OrderBundle::whereIn("id", $bundleIds)->update([
            "return_at" => $now
        ]);
        OrderBundleItem::whereIn("order_bundle_id", $bundleIds)->update([
            "return_at" => $now
        ]);
        // 设置bundle中资产的状态
        $bundleItems = OrderBundleItem::whereIn('order_bundle_id', $bundleIds)
            ->with(['rentalable'])
            ->get()
            ->each(function (OrderBundleItem $orderBundleItem) {
                $this->updateAssetStatusReturn($orderBundleItem->rentalable_type, $orderBundleItem->rentalable_id, $orderBundleItem->quantity);
            });
        $order = Order::findOrFail($id);
        $orderItems = $order->items;
        $orderItems->each(function (OrderItem $orderItem) {
            $this->updateAssetStatusReturn($orderItem->rentalable_type, $orderItem->rentalable_id, $orderItem->quantity);
        });
        $orderBundles = $order->bundles()->with(["items.rentalable"])->get();
        return response()->json(compact(['orderItems', 'orderBundles']));
    }


    public function queryAssetItems()
    {
        $query = request()->get('query');
        if (empty($query) || $query == "*") {
            return [];
        }
        $primaryCategoryIds = AssetCategory::where('name', 'ilike', $query)->Primary()->select('id')->get()->pluck("id");
        $secondaryCategoryIds = AssetCategory::where('name', 'ilike', $query)->Secondary()->select('id')->get()->pluck("id");
        $query = '%' . $query . '%';
        $assetQuery = Asset::where('name', 'ilike', $query)
            ->where('status_id', '=', AssetStatus::AVAILABLE);
        $primaryJoinedIds = $primaryCategoryIds->join(',');
        if ($primaryCategoryIds->count() > 0) {
            $assetQuery->orWhereRaw("(category_id in (?) and status_id = ?)", [$primaryJoinedIds, AssetStatus::AVAILABLE]);
        }
        $subCategoryJoinedIds = $secondaryCategoryIds->join(',');
        if ($primaryCategoryIds->count() > 0) {
            $assetQuery->orWhereRaw("(sub_category_id in (?) and status_id = ?)", [$subCategoryJoinedIds, AssetStatus::AVAILABLE]);
        }
        $assets = $assetQuery->limit(10)->get();
        $assetStocksQuery = AssetStock::where('name', 'ilike', $query)->where("available_quantity", '>', 0);
        if ($primaryCategoryIds->count() > 0) {
            $assetStocksQuery->orWhereRaw("(category_id in (?) and available_quantity > 0)", [$primaryJoinedIds]);
        }
        if ($primaryCategoryIds->count() > 0) {
            $assetStocksQuery->orWhereRaw("(sub_category_id in (?) and available_quantity > 0)", [$subCategoryJoinedIds]);
        }
        $assetStocks = $assetStocksQuery->limit(10)->get();
        $filterableBundles = FilterableAssetBundle::where('name', 'ilike', $query)->limit(10)->get();
        $bundles = $this->processBundles($filterableBundles);
        return response()->json(compact(['assets', 'assetStocks', 'bundles']));
    }

    public function createOrUpdate()
    {
        $_order = request()->get('order');
        $_orderItems = request()->get('orderItems');
        $_orderBundles = request()->get('orderBundles');
        $isNew = empty($_order['id']);
        if ($isNew) {
            $order = new Order();
            $order->order_number = OrderNumberGenerator::id();
            $order->status_id = 1; // 预览
        } else {
            $order = Order::findOrFail($_order['id']);
        }
        $order->customer_id = $_order['customer_id'];
        $order->description = $_order['description'];
        $order->expected_rental_out_date = $_order['expected_rental_out_date'];
        $order->expected_return_date = $_order['expected_return_date'];
        $order->expected_ship_date = $_order['expected_ship_date'];
        $order->payment_method_id = $_order['payment_method_id'];
        $order->is_paid = $_order['is_paid'];
        $order->paid_at = $_order['paid_at'];
        $order->net_amount = $_order['net_amount'];
        $order->paid_amount = $_order['paid_amount'];
        $order->payable_amount = $_order['payable_amount'];
        $order->ship_address = $_order['ship_address'];
        $order->identification_number = $_order['identification_number'];
        $order->location = $_order['location'];
        $order->discount = $_order['discount'];
        $order->created_by = auth()->user()->id;
        $order->assigned_to = auth()->user()->id;
        DB::beginTransaction();

        $order->save();
        $this->_processOldItems($order);
        $this->_processOldBundles($order);
        $toalAmount = $this->_processNewitems($_orderItems, $order);
        $bundleFee = $this->_processNewBundles($_orderBundles, $order);
        // TODO need add bundle fee
        $order->update([
            "net_amount" => $toalAmount + $bundleFee
        ]);
        DB::commit();

        $order = Order::with(["items", "bundles.items"])->where('id', $order->id)->get()->first();
        return response()->json(compact(['order']));
    }

    private function processBundles(Collection $filterableBundles)
    {
        $result = $filterableBundles->map(function ($bundle) {
            return [
                "id" => $bundle->id,
                "name" => $bundle->name,
                "items" => $this->retrieveCtiterions($bundle->criteria)
            ];
        });

        return $result;
    }

    public function paymentMethodList()
    {
        return KeyValueResource::collection(PaymentMethod::all());
    }

    public function fetchOrderItemsById($id)
    {
        return new CommonModelResource(Order::findOrFail($id)->items()->with("rentalable")->get());
    }

    public function fetchOrderBundlesById($id)
    {
        return new CommonModelResource(Order::findOrFail($id)->bundles()->with("items")->get());
    }

    public function printPDF($id)
    {
        $isDebug = request()->exists("debug");
        $order = Order::findOrFail($id);
        $items = $order->items;
        $data = compact(['order', 'items']);
        if ($isDebug) {
            return view("pdf.order.standard", $data);
        } else {
            $pdf = PDF::loadView('pdf.order.standard', $data);
            return $pdf->download('订单.pdf');
        }
    }

    private function retrieveCtiterions(Collection $criteria)
    {
        return $criteria->map(function ($criteria) {
            return [
                "criteria" => $criteria,
                "data" => $this->getCriterionQueryByFilterableAssetBundleId($criteria->id)->get()
            ];
//            return $this->getCriterionQueryByFilterableAssetBundleId($criteria->id)->get();
        });
    }

    /**
     * @param Order $order
     * @return array
     */
    private function _processOldItems(Order $order): array
    {
        $oldItems = $order->items;
        $oldAssets = $oldItems->filter(function (OrderItem $item) {
            return $item->rentalable_type == Asset::class;
        });
        $oldAssetStocks = $oldItems->filter(function (OrderItem $item) {
            return $item->rentalable_type == AssetStock::class;
        });
        $oldExtraFees = $oldItems->filter(function (OrderItem $item) {
            return $item->rentalable_type == OrderExtraFee::class;
        });


        $oldAssetIds = $oldAssets->pluck('rentalable_id');
        // 设置之前的Asset状态
        Asset::withTrashed()->whereIn('id', $oldAssetIds->toArray())->update(["status_id" => AssetStatus::AVAILABLE]);
        // 设置之前的AssetStock状态
        $oldAssetStocks->each(function (OrderItem $orderItem) {
            $stock = $orderItem->rentalable;
            $stock->update(["available_quantity" => $orderItem->quantity + $stock->available_quantity]);
        });
        // 删除所有之前的 ExtraFee
        $oldFeeIds = $oldExtraFees->pluck(['rentalable_id']);
        OrderExtraFee::destroy($oldFeeIds->toArray());
        $order->items()->delete(); // 删除所有之前的
        return array($oldAssets, $oldFeeIds);
    }


    private function getAssetsByType($orderItems, $type): \Illuminate\Support\Collection
    {
        return collect($orderItems)->filter(function ($item) use ($type) {
            return $item['rentalable_type'] == $type;
        });
    }

    /**
     * @param $_orderItems
     * @param $order
     */
    private function _processNewitems($_orderItems, $order): float
    {
        $totalAmount = 0;
        // 处理 Asset & AssetStock
        $assets = $this->getAssetsByType($_orderItems, Asset::class);
        $assetStocks = $this->getAssetsByType($_orderItems, AssetStock::class);
        $extraFees = $this->getAssetsByType($_orderItems, OrderExtraFee::class);

        foreach ($extraFees->toArray() as $fee) {
            // 创建额外费用
            $feeIns = OrderExtraFee::create([
                "name" => $fee["name"],
                "quantity" => $fee["quantity"],
                "unit_price" => $fee["unit_price"]
            ]);
            $feeIns->save();
            $orderItem = new OrderItem();
            $orderItem->order_id = $order->id;
            $orderItem->name = $fee['name'];
            $orderItem->rentalable_id = $feeIns->id;
            $orderItem->rentalable_type = $fee['rentalable_type'];
            $orderItem->quantity = $fee['quantity'];
            $orderItem->unit_price = $fee['unit_price'];
            $orderItem->rental_out_at = $fee['rental_out_at'];
            $orderItem->return_at = $fee['return_at'];
            $orderItem->amount = $fee['quantity'] * $fee['unit_price'];
            $orderItem->save();
            $totalAmount += $fee['quantity'] * $fee['unit_price'];
        }
        $normalAssets = $assets->concat($assetStocks)->toArray();
        foreach ($normalAssets as $_item) {
            $orderItem = new OrderItem();
            $orderItem->order_id = $order->id;
            $orderItem->name = $_item['name'];
            $rentalable_id = $_item['rentalable_id'];
            $orderItem->rentalable_id = $rentalable_id;
            $rentalable_type = $_item['rentalable_type'];
            $orderItem->rentalable_type = $rentalable_type;
            $quantity = $_item['quantity'];
            $orderItem->quantity = $quantity;
            $orderItem->unit_price = $_item['unit_price'];
            $orderItem->rental_out_at = $_item['rental_out_at'];
            $orderItem->return_at = $_item['return_at'];
            // 计算费用
            $amount = FeeFormulaUtils::calc($orderItem, $order);
            $totalAmount += $amount;
            $orderItem->amount = $amount;
            $orderItem->save();
            $this->updateAssetStatus($rentalable_type, $rentalable_id, $quantity);
        }
        return $totalAmount;
    }


    private function _processOldBundles(Order $order)
    {
        // 1. force delete all exit bundle items
        // 2. force delete all exit bundle
        $bundles = $order->bundles;
        $bundles->each(function (OrderBundle $bundle) {
            $bundle->items()->forceDelete();
        });
        $order->bundles()->forceDelete();

    }

    private function _processNewBundles($_orderBundles, Order $order): float
    {
        $totalBundleFee = 0;
        $_orderBundles = collect($_orderBundles);
        $_orderBundles->each(function ($_bundle) use ($order, &$totalBundleFee) {
            $orderBundle = new OrderBundle();
            $orderBundle->bundle_id = $_bundle['bundle_id'];
            $orderBundle->order_id = $order->id;
            $orderBundle->name = $_bundle['name'];
            $orderBundle->amount = $_bundle['amount'];
            $orderBundle->rental_out_at = $_bundle['rental_out_at'];
            $orderBundle->return_at = $_bundle['return_at'];
            $orderBundle->save();
            $totalBundleFee += $_bundle['amount'];

            // 处理 bundle item
            $bundleItem = new OrderBundleItem();
            $items = collect($_bundle['items']);
            $this->_processNewBundleItems($items, $orderBundle);

        });
        return $totalBundleFee;

    }

    private function _processNewBundleItems(\Illuminate\Support\Collection $items, OrderBundle $orderBundle)
    {
        $items->each(function ($item) use ($orderBundle) {
            $orderBundleItem = new OrderBundleItem();
            $orderBundleItem->name = $item['name'];
            $orderBundleItem->order_bundle_id = $orderBundle->id;
            $orderBundleItem->rentalable_id = $item['rentalable_id'];
            $orderBundleItem->rentalable_type = $item['rentalable_type'];
            $orderBundleItem->save();
            $this->updateAssetStatus($item['rentalable_type'], $item['rentalable_id'], $item['quantity']);
        });
    }

    /**
     * @param $rentalable_type
     * @param $rentalable_id
     * @param $quantity
     */
    private function updateAssetStatus($rentalable_type, $rentalable_id, $quantity): void
    {
        if ($rentalable_type == Asset::class) {
            $_asset = Asset::withTrashed()->findOrFail($rentalable_id);
            $_asset->update([
                "status_id" => AssetStatus::LEND
            ]);
        }
        if ($rentalable_type == AssetStock::class) {
            $_stock = AssetStock::withTrashed()->findOrFail($rentalable_id);
            $_stock->update([
                "available_quantity" => $_stock->available_quantity - $quantity
            ]);
        }
    }

    private function updateAssetStatusReturn($rentalable_type, $rentalable_id, $quantity): void
    {
        if ($rentalable_type == Asset::class) {
            $_asset = Asset::withTrashed()->findOrFail($rentalable_id);
            $_asset->update([
                "status_id" => AssetStatus::AVAILABLE
            ]);
        }
        if ($rentalable_type == AssetStock::class) {
            $_stock = AssetStock::withTrashed()->findOrFail($rentalable_id);
            $_stock->update([
                "available_quantity" => $_stock->available_quantity + $quantity
            ]);
        }
    }


}
